
#include <Camera.hpp>
#include <glm/gtc/random.hpp>

Camera::Camera() {
  float aspect_radio = 16.f / 9.f;
  float viewport_height = 2.f;
  float viewport_width = aspect_radio * viewport_height;
  float focal_length = 1.f;

  mOrigin = glm::vec3{0.f, 0.f, 0.f};
  mHorizontal = glm::vec3{viewport_width, 0.f, 0.f};
  mVertical = glm::vec3{0.f, viewport_height, 0.f};
  mLowerLeftCorner = mOrigin - mHorizontal / 2.f - mVertical / 2.f -
                     glm::vec3{0.f, 0.f, focal_length};
}

Camera::Camera(const glm::vec3& pos, const glm::vec3& target,
               const glm::vec3& vup, float vfov, float aspect_ratio) {
  float theta = glm::radians(vfov);
  float h = glm::tan(theta / 2.f);
  float viewport_height = 2.f * h;
  float viewport_width = aspect_ratio * viewport_height;

  glm::vec3 w = glm::normalize(pos - target);
  glm::vec3 u = glm::normalize(glm::cross(vup, w));
  glm::vec3 v = glm::cross(w, u);

  mOrigin = pos;
  mHorizontal = viewport_width * u;
  mVertical = viewport_height * v;
  mLowerLeftCorner = mOrigin - mHorizontal / 2.f - mVertical / 2.f - w;
}

Camera::Camera(const glm::vec3& pos, const glm::vec3& target,
               const glm::vec3& vup, float vfov, float aspect_ratio,
               float aperture, float focus_dist) {
  float theta = glm::radians(vfov);
  float h = glm::tan(theta / 2.f);
  float viewport_height = 2.f * h;
  float viewport_width = aspect_ratio * viewport_height;

  mW = glm::normalize(pos - target);
  mU = glm::normalize(glm::cross(vup, mW));
  mV = glm::cross(mW, mU);

  mOrigin = pos;
  mHorizontal = focus_dist * viewport_width * mU;
  mVertical = focus_dist * viewport_height * mV;
  mLowerLeftCorner =
      mOrigin - mHorizontal / 2.f - mVertical / 2.f - focus_dist * mW;

  mLensRadius = aperture / 2.f;
}

Ray Camera::GetRay(float u, float v) const {
  if (mLensRadius != 0.f) {
    glm::vec3 rd = mLensRadius * glm::linearRand(glm::vec3{-1.f, -1.f, 0.f},
                                                 glm::vec3{1.f, 1.f, 0.f});
    glm::vec3 offset = mU * rd.x + mV * rd.y;

    return Ray(mOrigin + offset, mLowerLeftCorner + u * mHorizontal +
                                     v * mVertical - mOrigin - offset);
  } else {
    return Ray(mOrigin,
               mLowerLeftCorner + u * mHorizontal + v * mVertical - mOrigin);
  }
}
